package views.folder
{
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.events.TimerEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.utils.Timer;
	import flash.utils.setTimeout;
	
	import gs.TweenLite;
	import gs.easing.Back;
	import gs.easing.Cubic;
	
	[Event(name="change",type="flash.events.Event")]
	[Event(name="complete",type="flash.events.Event")]
	public class FolderViewContext extends EventDispatcher
	{
		private const UNFOLD_SKEW:Number = 0.15;
		private const UNFOLD_WIDTH_RATE:Number = 0.95;
		private const FOLD_SKEW:Number = 0.3;
		
		private var displayWidth:Number;
		private var displayHeight:Number;
		private var colWidth:Number;
		private var rowHeight:Number;
		private var colCount:int;
		private var rowCount:int;
		
		private var maxOffset:Number;
		private var _offset:Number;
		
		private var currentCols:Vector.<FoldColumnModel>;
		private var allItems:Vector.<FoldColumnModel>;
		private var updateTimer:Timer;
		private var needUpdate:Boolean;
		
		private var lastOffset:Number;
		private var speed:Number = 0;
		private const UPDATE_DURATION:int = 30;
		private const EASE_DURATION:Number = 1.0;
		
		internal var currentItems:Vector.<FolderViewItemModel>;
		internal var folding:Boolean;
		internal var isFolded:Boolean;
		
		public function FolderViewContext()
		{
			updateTimer = new Timer(UPDATE_DURATION);
			updateTimer.addEventListener(TimerEvent.TIMER,updateContext);
		}
		
		private function updateContext(evt:Event):void
		{
			if(updateTimer.currentCount % 2 == 0)
			{
				if(!isNaN(lastOffset))
					speed = (offset - lastOffset) / (UPDATE_DURATION * 2 / 1000);
				lastOffset = offset;
			}
			
			if(!needUpdate)
				return;
			
			needUpdate = false;
			calcuteItemMatrix();
		}
		
		private function calcuteItemMatrix():void
		{
			var originPoints:Array = [new Point(0,0),new Point(0,rowHeight),new Point(colWidth,rowHeight)];
			currentItems = new Vector.<FolderViewItemModel>;
			var startX:Number = 0;
			
			for(var i:int = 0;i < currentCols.length;i++)
			{
				var colItems:FoldColumnModel = currentCols[i];
				var columnWidth:Number = colItems.columnWidth;
				
				for(var j:int = 0;j < colItems.items.length;j++)
				{
					var itemModel:FolderViewItemModel = colItems.items[j];
					
					var tranRate:Number = columnWidth / (colWidth * UNFOLD_WIDTH_RATE);
					var yChanged:Number = rowHeight * FOLD_SKEW +  (rowHeight * UNFOLD_SKEW - rowHeight * FOLD_SKEW) * tranRate;
			
					var pointTL:Point = new Point(startX,colItems.colIndex % 2 == 0 ? rowHeight * itemModel.rowIndex : rowHeight * itemModel.rowIndex + yChanged);
					var pointBL:Point = new Point(startX,colItems.colIndex % 2 == 0 ? rowHeight * (itemModel.rowIndex + 1) : rowHeight * (itemModel.rowIndex + 1) + yChanged);
					var pointBR:Point = new Point(startX + columnWidth, colItems.colIndex % 2 == 0 ? rowHeight * (itemModel.rowIndex+1) + yChanged : rowHeight * (itemModel.rowIndex + 1));
					
					
					itemModel.matrix = getMatrix([pointTL,pointBL,pointBR],originPoints);
					itemModel.alpha = columnWidth / (colWidth * UNFOLD_WIDTH_RATE);
					currentItems.push(itemModel);
				}
				startX += columnWidth;
			}
			dispatchEvent(new Event(Event.CHANGE));
		}
		
		
		private function getMatrix(points:Array,oriPoints:Array):Matrix
		{
			var a:Number = ( (points[0].x-points[1].x)*(oriPoints[1].y-oriPoints[2].y) - (points[1].x-points[2].x)*(oriPoints[0].y-oriPoints[1].y) ) /
				( (oriPoints[0].x-oriPoints[1].x)*(oriPoints[1].y-oriPoints[2].y) - (oriPoints[1].x-oriPoints[2].x)*(oriPoints[0].y-oriPoints[1].y) );
			var c:Number = ( (points[0].x - points[1].x) - (oriPoints[0].x - oriPoints[1].x) * a) / (oriPoints[0].y - oriPoints[1].y);
			var tx:Number = points[0].x - c * oriPoints[0].y - a * oriPoints[0].x;
			
			var b:Number = ( (points[0].y-points[1].y)*(oriPoints[1].y-oriPoints[2].y) - (points[1].y-points[2].y)*(oriPoints[0].y-oriPoints[1].y) ) /
				( (oriPoints[0].x-oriPoints[1].x)*(oriPoints[1].y-oriPoints[2].y) - (oriPoints[1].x-oriPoints[2].x)*(oriPoints[0].y-oriPoints[1].y) );
			var d:Number = ( (points[0].y - points[1].y) - (oriPoints[0].x - oriPoints[1].x) * b) / (oriPoints[0].y - oriPoints[1].y);
			var ty:Number = points[0].y - d * oriPoints[0].y - b * oriPoints[0].x;
			
			
			return new Matrix(a,b,c,d,tx,ty);
		} 
		
		public function set offset(_offset:Number):void
		{
			if(_offset > maxOffset || _offset < 0 || 
			   _offset == offset || isFolded || folding)
				return;
			
			this._offset = _offset;
			
			updateTimer.start();
			needUpdate = true;
			
			var colStartIndex:int = Math.floor(offset / (colWidth * UNFOLD_WIDTH_RATE));
			colStartIndex = colStartIndex % 2 == 0 ? colStartIndex : colStartIndex - 1;
			
			var colEndIndex:int = Math.floor((offset + displayWidth) / (colWidth * UNFOLD_WIDTH_RATE));
			colEndIndex = Math.min(colEndIndex,colCount - 1);
			
			currentCols = allItems.slice(colStartIndex,colEndIndex + 1);
			for (var i:int = 0;i < currentCols.length;i++)
			{
				var column:FoldColumnModel = currentCols[i];
				if(i == 0)
				{
					if(column.colIndex % 2 == 0 && currentCols.length > 1)
					{
						column.columnWidth = ((colWidth * UNFOLD_WIDTH_RATE) * (column.colIndex + 1) - offset + (colWidth * UNFOLD_WIDTH_RATE)) / 2;
						column.unfoldWidth = column.columnWidth;
						i++;
						var nextColumn:FoldColumnModel = currentCols[i];
						nextColumn.columnWidth = column.columnWidth;
						nextColumn.unfoldWidth = column.columnWidth;
					}else{
						column.columnWidth = (colWidth * UNFOLD_WIDTH_RATE) * (column.colIndex + 1) - offset;
						column.unfoldWidth = column.columnWidth;
					}
					
				}else{
					column.columnWidth = colWidth * UNFOLD_WIDTH_RATE;
					column.unfoldWidth = column.columnWidth;
				}
			}
		}
		
		public function get offset():Number
		{
			return _offset;
		}
		
		internal function initialize(displayWidth:Number,displayHeight:Number,
									 colWidth:Number,rowHeight:Number,colCount:int,rowCount:int):void
		{
			this.displayWidth = displayWidth;
			this.displayHeight = displayHeight;
			this.colWidth = colWidth;
			this.rowHeight = rowHeight;;
			this.rowCount = rowCount;
			this.colCount = colCount;
			
			maxOffset =  (colWidth * colCount * UNFOLD_WIDTH_RATE) > displayWidth ? (colWidth * colCount * UNFOLD_WIDTH_RATE) - displayWidth : 0;
			maxOffset = Math.ceil(maxOffset / (colWidth * UNFOLD_WIDTH_RATE * 2)) * (colWidth * UNFOLD_WIDTH_RATE * 2);
			
			allItems = new Vector.<FoldColumnModel>;
			for(var i:int = 0;i < colCount;i++)
			{
				var colItems:FoldColumnModel = new FoldColumnModel;
				colItems.items = new Vector.<FolderViewItemModel>;
				colItems.colIndex = i;
				
				for(var j:int = 0; j < rowCount;j++)
				{
					var item:FolderViewItemModel = new FolderViewItemModel;
					
					item.colIndex = i;
					item.rowIndex = j;
					
					colItems.items.push(item);
				}
				allItems.push(colItems);
			}
						
			offset = 0;
			for each(var columnModel:FoldColumnModel in currentCols)
				columnModel.columnWidth = 0;
			isFolded = true;
			
			updateTimer.start();
		}
		
		internal function easeOut():void
		{
			var a:Number = (0 - speed) / EASE_DURATION;
			var s:Number = speed * EASE_DURATION + a * EASE_DURATION * EASE_DURATION / 2;
			var endS:Number = offset + s;
			endS = Math.round(endS / (colWidth * UNFOLD_WIDTH_RATE * 2)) * (colWidth * UNFOLD_WIDTH_RATE * 2) ;
			endS = Math.max(0,endS);
			endS = Math.min(endS,maxOffset);
			s = endS - offset;
			TweenLite.to(this,EASE_DURATION,{offset:offset + s,ease:Cubic.easeOut});
				
		}
		
		internal function reset():void
		{
			updateTimer.stop();
			_offset = NaN;
			lastOffset = NaN
			speed = 0;
			isFolded = false;
			folding = false;
			
			allItems = new Vector.<FoldColumnModel>;
			currentCols = new Vector.<FoldColumnModel>;
			currentItems = new Vector.<FolderViewItemModel>;
		}
		
		internal function unfold(animated:Boolean = true):void
		{
			if(!isFolded)
				return
			isFolded = false;	
			TweenLite.killTweensOf(this);

			if(animated)
			{
				folding = true;
				for each(var columnModel:FoldColumnModel in currentCols)
					TweenLite.to(columnModel,EASE_DURATION,{columnWidth:columnModel.unfoldWidth,ease:Back.easeInOut,
						onUpdate:calcuteItemMatrix});
					
				setTimeout(function():void{
					folding = false;
					isFolded = false;
					dispatchEvent(new Event(Event.COMPLETE));},EASE_DURATION * 1000);
			}else{
				
				for each(var columnModelTemp:FoldColumnModel in currentCols)
					columnModelTemp.columnWidth = columnModelTemp.unfoldWidth;
				calcuteItemMatrix();	
				dispatchEvent(new Event(Event.COMPLETE));
			}
		}
		
		
		internal function fold(animated:Boolean = true):void
		{
			if(isFolded)
				return
			isFolded = true;
			TweenLite.killTweensOf(this);
			
			if(animated)
			{
				folding = true;
				for each(var columnModel:FoldColumnModel in currentCols)
					TweenLite.to(columnModel,EASE_DURATION,{columnWidth:0,ease:Back.easeInOut,
						onUpdate:calcuteItemMatrix});
				
				setTimeout(function():void{
					folding = false;
					isFolded = true;
					dispatchEvent(new Event(Event.COMPLETE));},EASE_DURATION * 1000);
			}else{
				
				for each(var columnModelTemp:FoldColumnModel in currentCols)
				columnModelTemp.columnWidth = 0;
				calcuteItemMatrix();	
				dispatchEvent(new Event(Event.COMPLETE));
			}
			
		}
		
	}
}